From 103f369cee209f6f0a15ef953cff138ffa025a26 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 8 Apr 2026 21:59:10 +0300 Subject: task b: protect regex cache with RWMutex --- internal/ui/helpers_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) (limited to 'internal/ui/helpers_test.go') diff --git a/internal/ui/helpers_test.go b/internal/ui/helpers_test.go index 9760277..1fe853b 100644 --- a/internal/ui/helpers_test.go +++ b/internal/ui/helpers_test.go @@ -3,7 +3,9 @@ package ui import ( "fmt" "reflect" + "regexp" "strings" + "sync" "testing" "time" @@ -98,6 +100,52 @@ func TestFormatDueText(t *testing.T) { } } +func TestSearchRegexCacheConcurrentAccess(t *testing.T) { + searchRegexMu.Lock() + searchRegexCache = make(map[string]*regexp.Regexp) + searchRegexMu.Unlock() + t.Cleanup(func() { + searchRegexMu.Lock() + searchRegexCache = make(map[string]*regexp.Regexp) + searchRegexMu.Unlock() + }) + + patterns := []string{`alpha`, `beta`} + var wg sync.WaitGroup + errCh := make(chan error, 128) + + for i := 0; i < 16; i++ { + for _, pattern := range patterns { + wg.Add(1) + go func(pattern string) { + defer wg.Done() + + re, err := compileAndCacheRegex(pattern) + if err != nil { + errCh <- err + return + } + if re == nil || !re.MatchString(pattern) { + errCh <- fmt.Errorf("compiled regex for %q did not match", pattern) + return + } + if cached, ok := cachedSearchRegex(pattern); !ok || cached == nil { + errCh <- fmt.Errorf("missing cached regex for %q", pattern) + } + }(pattern) + } + } + + wg.Wait() + close(errCh) + + for err := range errCh { + if err != nil { + t.Fatal(err) + } + } +} + func TestValidateTagName(t *testing.T) { tests := []struct { name string @@ -414,6 +462,7 @@ func TestValidateRecurrence(t *testing.T) { }) } } + // TestParseFilterInput verifies that parseFilterInput correctly handles // taskwarrior filter expressions, including attribute filters (proj:xxx), // tag filters (+tag), quoted values (description:"some text"), and empty input. -- cgit v1.2.3