Worker set its working directory (#8461)

* set working directory

* consolidate to worker directory

* working directory

* correct directory name

* refactoring to use wildcard matcher

* simplify

* cleaning ec working directory

* fix reference

* clean

* adjust test
This commit is contained in:
Chris Lu
2026-02-27 12:22:21 -08:00
committed by GitHub
parent cf3b7b3ad7
commit 4f647e1036
23 changed files with 559 additions and 815 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
)
// LRUNode represents a node in the doubly-linked list for efficient LRU operations
@@ -210,7 +211,7 @@ func (e *StringLikeEvaluator) Evaluate(conditionValue interface{}, contextValues
patterns := getCachedNormalizedValues(conditionValue)
for _, pattern := range patterns {
for _, contextValue := range contextValues {
if MatchesWildcard(pattern, contextValue) {
if wildcard.MatchesWildcard(pattern, contextValue) {
return true
}
}
@@ -225,7 +226,7 @@ func (e *StringNotLikeEvaluator) Evaluate(conditionValue interface{}, contextVal
patterns := getCachedNormalizedValues(conditionValue)
for _, pattern := range patterns {
for _, contextValue := range contextValues {
if MatchesWildcard(pattern, contextValue) {
if wildcard.MatchesWildcard(pattern, contextValue) {
return false
}
}
@@ -628,7 +629,7 @@ func (e *ArnLikeEvaluator) Evaluate(conditionValue interface{}, contextValues []
patterns := getCachedNormalizedValues(conditionValue)
for _, pattern := range patterns {
for _, contextValue := range contextValues {
if MatchesWildcard(pattern, contextValue) {
if wildcard.MatchesWildcard(pattern, contextValue) {
return true
}
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
)
// tagsToEntry converts a map of tag key-value pairs to the entry.Extended format
@@ -749,7 +750,7 @@ func TestWildcardMatching(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := MatchesWildcard(tt.pattern, tt.str)
result := wildcard.MatchesWildcard(tt.pattern, tt.str)
if result != tt.expected {
t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, tt.str, tt.expected, result)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/seaweedfs/seaweedfs/weed/glog"
s3const "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
)
// Policy Engine Types
@@ -180,9 +181,9 @@ type CompiledPolicy struct {
// CompiledStatement represents a compiled policy statement
type CompiledStatement struct {
Statement *PolicyStatement
ActionMatchers []*WildcardMatcher
ResourceMatchers []*WildcardMatcher
PrincipalMatchers []*WildcardMatcher
ActionMatchers []*wildcard.WildcardMatcher
ResourceMatchers []*wildcard.WildcardMatcher
PrincipalMatchers []*wildcard.WildcardMatcher
// Keep regex patterns for backward compatibility
ActionPatterns []*regexp.Regexp
ResourcePatterns []*regexp.Regexp
@@ -195,7 +196,7 @@ type CompiledStatement struct {
// NotResource patterns (resource should NOT match these)
NotResourcePatterns []*regexp.Regexp
NotResourceMatchers []*WildcardMatcher
NotResourceMatchers []*wildcard.WildcardMatcher
DynamicNotResourcePatterns []string
}
@@ -328,7 +329,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
}
compiled.ActionPatterns = append(compiled.ActionPatterns, pattern)
matcher, err := NewWildcardMatcher(action)
matcher, err := wildcard.NewWildcardMatcher(action)
if err != nil {
return nil, fmt.Errorf("failed to create action matcher %s: %v", action, err)
}
@@ -352,7 +353,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
}
compiled.ResourcePatterns = append(compiled.ResourcePatterns, pattern)
matcher, err := NewWildcardMatcher(resource)
matcher, err := wildcard.NewWildcardMatcher(resource)
if err != nil {
return nil, fmt.Errorf("failed to create resource matcher %s: %v", resource, err)
}
@@ -377,7 +378,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
}
compiled.PrincipalPatterns = append(compiled.PrincipalPatterns, pattern)
matcher, err := NewWildcardMatcher(principal)
matcher, err := wildcard.NewWildcardMatcher(principal)
if err != nil {
return nil, fmt.Errorf("failed to create principal matcher %s: %v", principal, err)
}
@@ -403,7 +404,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
}
compiled.NotResourcePatterns = append(compiled.NotResourcePatterns, pattern)
matcher, err := NewWildcardMatcher(notResource)
matcher, err := wildcard.NewWildcardMatcher(notResource)
if err != nil {
return nil, fmt.Errorf("failed to create NotResource matcher %s: %v", notResource, err)
}
@@ -419,7 +420,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
// compilePattern compiles a wildcard pattern to regex
func compilePattern(pattern string) (*regexp.Regexp, error) {
return CompileWildcardPattern(pattern)
return wildcard.CompileWildcardPattern(pattern)
}
// normalizeToStringSlice converts various types to string slice - kept for backward compatibility
@@ -571,11 +572,11 @@ func (cp *CompiledPolicy) EvaluatePolicy(args *PolicyEvaluationArgs) (bool, Poli
// FastMatchesWildcard uses cached WildcardMatcher for performance
func FastMatchesWildcard(pattern, str string) bool {
matcher, err := GetCachedWildcardMatcher(pattern)
matcher, err := wildcard.GetCachedWildcardMatcher(pattern)
if err != nil {
glog.Errorf("Error getting cached WildcardMatcher for pattern %s: %v", pattern, err)
// Fall back to the original implementation
return MatchesWildcard(pattern, str)
return wildcard.MatchesWildcard(pattern, str)
}
return matcher.Match(str)
}

View File

@@ -1,252 +0,0 @@
package policy_engine
import (
"regexp"
"strings"
"sync"
"github.com/seaweedfs/seaweedfs/weed/glog"
)
// WildcardMatcher provides unified wildcard matching functionality
type WildcardMatcher struct {
// Use regex for complex patterns with ? wildcards
// Use string manipulation for simple * patterns (better performance)
useRegex bool
regex *regexp.Regexp
pattern string
}
// WildcardMatcherCache provides caching for WildcardMatcher instances
type WildcardMatcherCache struct {
mu sync.RWMutex
matchers map[string]*WildcardMatcher
maxSize int
accessOrder []string // For LRU eviction
}
// NewWildcardMatcherCache creates a new WildcardMatcherCache with a configurable maxSize
func NewWildcardMatcherCache(maxSize int) *WildcardMatcherCache {
if maxSize <= 0 {
maxSize = 1000 // Default value
}
return &WildcardMatcherCache{
matchers: make(map[string]*WildcardMatcher),
maxSize: maxSize,
}
}
// Global cache instance
var wildcardMatcherCache = NewWildcardMatcherCache(1000) // Default maxSize
// GetCachedWildcardMatcher gets or creates a cached WildcardMatcher for the given pattern
func GetCachedWildcardMatcher(pattern string) (*WildcardMatcher, error) {
// Fast path: check if already in cache
wildcardMatcherCache.mu.RLock()
if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
wildcardMatcherCache.mu.RUnlock()
wildcardMatcherCache.updateAccessOrder(pattern)
return matcher, nil
}
wildcardMatcherCache.mu.RUnlock()
// Slow path: create new matcher and cache it
wildcardMatcherCache.mu.Lock()
defer wildcardMatcherCache.mu.Unlock()
// Double-check after acquiring write lock
if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
wildcardMatcherCache.updateAccessOrderLocked(pattern)
return matcher, nil
}
// Create new matcher
matcher, err := NewWildcardMatcher(pattern)
if err != nil {
return nil, err
}
// Evict old entries if cache is full
if len(wildcardMatcherCache.matchers) >= wildcardMatcherCache.maxSize {
wildcardMatcherCache.evictLeastRecentlyUsed()
}
// Cache it
wildcardMatcherCache.matchers[pattern] = matcher
wildcardMatcherCache.accessOrder = append(wildcardMatcherCache.accessOrder, pattern)
return matcher, nil
}
// updateAccessOrder updates the access order for LRU eviction (with read lock)
func (c *WildcardMatcherCache) updateAccessOrder(pattern string) {
c.mu.Lock()
defer c.mu.Unlock()
c.updateAccessOrderLocked(pattern)
}
// updateAccessOrderLocked updates the access order for LRU eviction (without locking)
func (c *WildcardMatcherCache) updateAccessOrderLocked(pattern string) {
// Remove pattern from its current position
for i, p := range c.accessOrder {
if p == pattern {
c.accessOrder = append(c.accessOrder[:i], c.accessOrder[i+1:]...)
break
}
}
// Add pattern to the end (most recently used)
c.accessOrder = append(c.accessOrder, pattern)
}
// evictLeastRecentlyUsed removes the least recently used pattern from the cache
func (c *WildcardMatcherCache) evictLeastRecentlyUsed() {
if len(c.accessOrder) == 0 {
return
}
// Remove the least recently used pattern (first in the list)
lruPattern := c.accessOrder[0]
c.accessOrder = c.accessOrder[1:]
delete(c.matchers, lruPattern)
}
// ClearCache clears all cached patterns (useful for testing)
func (c *WildcardMatcherCache) ClearCache() {
c.mu.Lock()
defer c.mu.Unlock()
c.matchers = make(map[string]*WildcardMatcher)
c.accessOrder = c.accessOrder[:0]
}
// GetCacheStats returns cache statistics
func (c *WildcardMatcherCache) GetCacheStats() (size int, maxSize int) {
c.mu.RLock()
defer c.mu.RUnlock()
return len(c.matchers), c.maxSize
}
// NewWildcardMatcher creates a new wildcard matcher for the given pattern
// The matcher uses an efficient string-based algorithm that handles both * and ? wildcards
// without requiring regex compilation.
func NewWildcardMatcher(pattern string) (*WildcardMatcher, error) {
matcher := &WildcardMatcher{
pattern: pattern,
useRegex: false, // String-based matching now handles both * and ?
}
return matcher, nil
}
// Match checks if a string matches the wildcard pattern
func (m *WildcardMatcher) Match(str string) bool {
if m.useRegex {
return m.regex.MatchString(str)
}
return matchWildcardString(m.pattern, str)
}
// MatchesWildcard provides a simple function interface for wildcard matching
// This function consolidates the logic from the previous separate implementations
//
// Rules:
// - '*' matches any sequence of characters (including empty string)
// - '?' matches exactly one character (any character)
func MatchesWildcard(pattern, str string) bool {
// matchWildcardString now handles both * and ? efficiently without regex
return matchWildcardString(pattern, str)
}
// CompileWildcardPattern converts a wildcard pattern to a compiled regex
// This replaces the previous compilePattern function
func CompileWildcardPattern(pattern string) (*regexp.Regexp, error) {
return compileWildcardPattern(pattern)
}
// matchWildcardString uses efficient string manipulation for * and ? wildcards
// This implementation uses a backtracking algorithm that handles both wildcard types
// without requiring regex compilation.
//
// Rules:
// - '*' matches any sequence of characters (including empty string)
// - '?' matches exactly one character (any character)
func matchWildcardString(pattern, str string) bool {
// Handle simple cases
if pattern == "*" {
return true
}
if pattern == str {
return true
}
targetIndex := 0
patternIndex := 0
// Index of the most recent '*' in the pattern (-1 if none)
lastStarIndex := -1
// Index in target where the last '*' started matching
lastStarMatchIndex := 0
for targetIndex < len(str) {
switch {
// Case 1: Current characters match directly or '?' matches any single character
case patternIndex < len(pattern) &&
(pattern[patternIndex] == '?' || pattern[patternIndex] == str[targetIndex]):
targetIndex++
patternIndex++
// Case 2: Wildcard '*' found in pattern
case patternIndex < len(pattern) &&
pattern[patternIndex] == '*':
lastStarIndex = patternIndex
lastStarMatchIndex = targetIndex
patternIndex++
// Case 3: Previous '*' can absorb one more character
case lastStarIndex != -1:
patternIndex = lastStarIndex + 1
lastStarMatchIndex++
targetIndex = lastStarMatchIndex
// Case 4: No match possible
default:
return false
}
}
// Consume any trailing '*' in the pattern
for patternIndex < len(pattern) && pattern[patternIndex] == '*' {
patternIndex++
}
// Match is valid only if the entire pattern is consumed
return patternIndex == len(pattern)
}
// matchWildcardRegex uses WildcardMatcher for patterns with ? wildcards
func matchWildcardRegex(pattern, str string) bool {
matcher, err := GetCachedWildcardMatcher(pattern)
if err != nil {
glog.Errorf("Error getting WildcardMatcher for pattern %s: %v. Falling back to matchWildcardString.", pattern, err)
// Fallback to matchWildcardString
return matchWildcardString(pattern, str)
}
return matcher.Match(str)
}
// compileWildcardPattern converts a wildcard pattern to regex
func compileWildcardPattern(pattern string) (*regexp.Regexp, error) {
// Escape special regex characters except * and ?
escaped := regexp.QuoteMeta(pattern)
// Replace escaped wildcards with regex equivalents
escaped = strings.ReplaceAll(escaped, `\*`, `.*`)
escaped = strings.ReplaceAll(escaped, `\?`, `.`)
// Anchor the pattern
escaped = "^" + escaped + "$"
return regexp.Compile(escaped)
}

View File

@@ -1,469 +0,0 @@
package policy_engine
import (
"testing"
)
func TestMatchesWildcard(t *testing.T) {
tests := []struct {
name string
pattern string
str string
expected bool
}{
// Basic functionality tests
{
name: "Exact match",
pattern: "test",
str: "test",
expected: true,
},
{
name: "Single wildcard",
pattern: "*",
str: "anything",
expected: true,
},
{
name: "Empty string with wildcard",
pattern: "*",
str: "",
expected: true,
},
// Star (*) wildcard tests
{
name: "Prefix wildcard",
pattern: "test*",
str: "test123",
expected: true,
},
{
name: "Suffix wildcard",
pattern: "*test",
str: "123test",
expected: true,
},
{
name: "Middle wildcard",
pattern: "test*123",
str: "testABC123",
expected: true,
},
{
name: "Multiple wildcards",
pattern: "test*abc*123",
str: "testXYZabcDEF123",
expected: true,
},
{
name: "No match",
pattern: "test*",
str: "other",
expected: false,
},
// Question mark (?) wildcard tests
{
name: "Single question mark",
pattern: "test?",
str: "test1",
expected: true,
},
{
name: "Multiple question marks",
pattern: "test??",
str: "test12",
expected: true,
},
{
name: "Question mark no match",
pattern: "test?",
str: "test12",
expected: false,
},
{
name: "Mixed wildcards",
pattern: "test*abc?def",
str: "testXYZabc1def",
expected: true,
},
// Edge cases
{
name: "Empty pattern",
pattern: "",
str: "",
expected: true,
},
{
name: "Empty pattern with string",
pattern: "",
str: "test",
expected: false,
},
{
name: "Pattern with string empty",
pattern: "test",
str: "",
expected: false,
},
// Special characters
{
name: "Pattern with regex special chars",
pattern: "test[abc]",
str: "test[abc]",
expected: true,
},
{
name: "Pattern with dots",
pattern: "test.txt",
str: "test.txt",
expected: true,
},
{
name: "Pattern with dots and wildcard",
pattern: "*.txt",
str: "test.txt",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := MatchesWildcard(tt.pattern, tt.str)
if result != tt.expected {
t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, tt.str, tt.expected, result)
}
})
}
}
func TestWildcardMatcher(t *testing.T) {
tests := []struct {
name string
pattern string
strings []string
expected []bool
}{
{
name: "Simple star pattern",
pattern: "test*",
strings: []string{"test", "test123", "testing", "other"},
expected: []bool{true, true, true, false},
},
{
name: "Question mark pattern",
pattern: "test?",
strings: []string{"test1", "test2", "test", "test12"},
expected: []bool{true, true, false, false},
},
{
name: "Mixed pattern",
pattern: "*.txt",
strings: []string{"file.txt", "test.txt", "file.doc", "txt"},
expected: []bool{true, true, false, false},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matcher, err := NewWildcardMatcher(tt.pattern)
if err != nil {
t.Fatalf("Failed to create matcher: %v", err)
}
for i, str := range tt.strings {
result := matcher.Match(str)
if result != tt.expected[i] {
t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, str, tt.expected[i], result)
}
}
})
}
}
func TestCompileWildcardPattern(t *testing.T) {
tests := []struct {
name string
pattern string
input string
want bool
}{
{"Star wildcard", "s3:Get*", "s3:GetObject", true},
{"Question mark wildcard", "s3:Get?bject", "s3:GetObject", true},
{"Mixed wildcards", "s3:*Object*", "s3:GetObjectAcl", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
regex, err := CompileWildcardPattern(tt.pattern)
if err != nil {
t.Errorf("CompileWildcardPattern() error = %v", err)
return
}
got := regex.MatchString(tt.input)
if got != tt.want {
t.Errorf("CompileWildcardPattern() = %v, want %v", got, tt.want)
}
})
}
}
// BenchmarkWildcardMatchingPerformance demonstrates the performance benefits of caching
func BenchmarkWildcardMatchingPerformance(b *testing.B) {
patterns := []string{
"s3:Get*",
"s3:Put*",
"s3:Delete*",
"s3:List*",
"arn:aws:s3:::bucket/*",
"arn:aws:s3:::bucket/prefix*",
"user:*",
"user:admin-*",
}
inputs := []string{
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket",
"arn:aws:s3:::bucket/file.txt",
"arn:aws:s3:::bucket/prefix/file.txt",
"user:admin",
"user:admin-john",
}
b.Run("WithoutCache", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, pattern := range patterns {
for _, input := range inputs {
MatchesWildcard(pattern, input)
}
}
}
})
b.Run("WithCache", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, pattern := range patterns {
for _, input := range inputs {
FastMatchesWildcard(pattern, input)
}
}
}
})
}
// BenchmarkWildcardMatcherReuse demonstrates the performance benefits of reusing WildcardMatcher instances
func BenchmarkWildcardMatcherReuse(b *testing.B) {
pattern := "s3:Get*"
input := "s3:GetObject"
b.Run("NewMatcherEveryTime", func(b *testing.B) {
for i := 0; i < b.N; i++ {
matcher, _ := NewWildcardMatcher(pattern)
matcher.Match(input)
}
})
b.Run("CachedMatcher", func(b *testing.B) {
for i := 0; i < b.N; i++ {
matcher, _ := GetCachedWildcardMatcher(pattern)
matcher.Match(input)
}
})
}
// TestWildcardMatcherCaching verifies that caching works correctly
func TestWildcardMatcherCaching(t *testing.T) {
pattern := "s3:Get*"
// Get the first matcher
matcher1, err := GetCachedWildcardMatcher(pattern)
if err != nil {
t.Fatalf("Failed to get cached matcher: %v", err)
}
// Get the second matcher - should be the same instance
matcher2, err := GetCachedWildcardMatcher(pattern)
if err != nil {
t.Fatalf("Failed to get cached matcher: %v", err)
}
// Check that they're the same instance (same pointer)
if matcher1 != matcher2 {
t.Errorf("Expected same matcher instance, got different instances")
}
// Test that both matchers work correctly
testInput := "s3:GetObject"
if !matcher1.Match(testInput) {
t.Errorf("First matcher failed to match %s", testInput)
}
if !matcher2.Match(testInput) {
t.Errorf("Second matcher failed to match %s", testInput)
}
}
// TestFastMatchesWildcard verifies that the fast matching function works correctly
func TestFastMatchesWildcard(t *testing.T) {
tests := []struct {
pattern string
input string
want bool
}{
{"s3:Get*", "s3:GetObject", true},
{"s3:Put*", "s3:GetObject", false},
{"arn:aws:s3:::bucket/*", "arn:aws:s3:::bucket/file.txt", true},
{"user:admin-*", "user:admin-john", true},
{"user:admin-*", "user:guest-john", false},
}
for _, tt := range tests {
t.Run(tt.pattern+"_"+tt.input, func(t *testing.T) {
got := FastMatchesWildcard(tt.pattern, tt.input)
if got != tt.want {
t.Errorf("FastMatchesWildcard(%q, %q) = %v, want %v", tt.pattern, tt.input, got, tt.want)
}
})
}
}
// TestWildcardMatcherCacheBounding tests the bounded cache functionality
func TestWildcardMatcherCacheBounding(t *testing.T) {
// Clear cache before test
wildcardMatcherCache.ClearCache()
// Get original max size
originalMaxSize := wildcardMatcherCache.maxSize
// Set a small max size for testing
wildcardMatcherCache.maxSize = 3
defer func() {
wildcardMatcherCache.maxSize = originalMaxSize
wildcardMatcherCache.ClearCache()
}()
// Add patterns up to max size
patterns := []string{"pattern1", "pattern2", "pattern3"}
for _, pattern := range patterns {
_, err := GetCachedWildcardMatcher(pattern)
if err != nil {
t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
}
}
// Verify cache size
size, maxSize := wildcardMatcherCache.GetCacheStats()
if size != 3 {
t.Errorf("Expected cache size 3, got %d", size)
}
if maxSize != 3 {
t.Errorf("Expected max size 3, got %d", maxSize)
}
// Add another pattern, should evict the least recently used
_, err := GetCachedWildcardMatcher("pattern4")
if err != nil {
t.Fatalf("Failed to get cached matcher for pattern4: %v", err)
}
// Cache should still be at max size
size, _ = wildcardMatcherCache.GetCacheStats()
if size != 3 {
t.Errorf("Expected cache size 3 after eviction, got %d", size)
}
// The first pattern should have been evicted
wildcardMatcherCache.mu.RLock()
if _, exists := wildcardMatcherCache.matchers["pattern1"]; exists {
t.Errorf("Expected pattern1 to be evicted, but it still exists")
}
if _, exists := wildcardMatcherCache.matchers["pattern4"]; !exists {
t.Errorf("Expected pattern4 to be in cache, but it doesn't exist")
}
wildcardMatcherCache.mu.RUnlock()
}
// TestWildcardMatcherCacheLRU tests the LRU eviction policy
func TestWildcardMatcherCacheLRU(t *testing.T) {
// Clear cache before test
wildcardMatcherCache.ClearCache()
// Get original max size
originalMaxSize := wildcardMatcherCache.maxSize
// Set a small max size for testing
wildcardMatcherCache.maxSize = 3
defer func() {
wildcardMatcherCache.maxSize = originalMaxSize
wildcardMatcherCache.ClearCache()
}()
// Add patterns to fill cache
patterns := []string{"pattern1", "pattern2", "pattern3"}
for _, pattern := range patterns {
_, err := GetCachedWildcardMatcher(pattern)
if err != nil {
t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
}
}
// Access pattern1 to make it most recently used
_, err := GetCachedWildcardMatcher("pattern1")
if err != nil {
t.Fatalf("Failed to access pattern1: %v", err)
}
// Add another pattern, should evict pattern2 (now least recently used)
_, err = GetCachedWildcardMatcher("pattern4")
if err != nil {
t.Fatalf("Failed to get cached matcher for pattern4: %v", err)
}
// pattern1 should still be in cache (was accessed recently)
// pattern2 should be evicted (was least recently used)
wildcardMatcherCache.mu.RLock()
if _, exists := wildcardMatcherCache.matchers["pattern1"]; !exists {
t.Errorf("Expected pattern1 to remain in cache (most recently used)")
}
if _, exists := wildcardMatcherCache.matchers["pattern2"]; exists {
t.Errorf("Expected pattern2 to be evicted (least recently used)")
}
if _, exists := wildcardMatcherCache.matchers["pattern3"]; !exists {
t.Errorf("Expected pattern3 to remain in cache")
}
if _, exists := wildcardMatcherCache.matchers["pattern4"]; !exists {
t.Errorf("Expected pattern4 to be in cache")
}
wildcardMatcherCache.mu.RUnlock()
}
// TestWildcardMatcherCacheClear tests the cache clearing functionality
func TestWildcardMatcherCacheClear(t *testing.T) {
// Add some patterns to cache
patterns := []string{"pattern1", "pattern2", "pattern3"}
for _, pattern := range patterns {
_, err := GetCachedWildcardMatcher(pattern)
if err != nil {
t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
}
}
// Verify cache has patterns
size, _ := wildcardMatcherCache.GetCacheStats()
if size == 0 {
t.Errorf("Expected cache to have patterns before clearing")
}
// Clear cache
wildcardMatcherCache.ClearCache()
// Verify cache is empty
size, _ = wildcardMatcherCache.GetCacheStats()
if size != 0 {
t.Errorf("Expected cache to be empty after clearing, got size %d", size)
}
}