Files
seaweedfs/weed/s3api/s3lifecycle/engine/compile_helpers_test.go
Chris LuandGitHub b740e22e63 test(s3/lifecycle): bundle dispatcher + engine edge-case coverage (#9413)
* test(s3/lifecycle): bundle dispatcher + engine edge-case coverage

Two-package bundle covering uncovered branches in production code that
the existing happy-path tests don't reach. Dispatcher 58.1% → 60.2%
and engine 81.0% → 81.7% (engine lift modest because most branches
were already hit; the nil-rule defensive case is otherwise unreachable
from a Compile flow).

dispatcher (4 tests):
- FilerPersister.Load with nil Store errors with a "nil Store"
  message rather than panicking at the Read call.
- FilerPersister.Save with nil Store same.
- FilerPersister.Load with a non-NotFound transport error wraps the
  shard ID into the message AND keeps the underlying error
  recoverable via errors.Is.
- FilerPersister.Load with successful empty []byte returns an empty
  map, not a JSON-decode error — pinning that an existing-but-empty
  cursor file is treated as "no entries".
- Tick initializes the retries map on first call without panic so a
  freshly-constructed Dispatcher works.
- Tick with already-canceled ctx re-queues the popped Match, returns
  zero, and never invokes the LifecycleDelete client — the Match
  must not be lost across worker restart.

engine (4 tests):
- rulePredicateSensitive(nil) returns false rather than panicking on
  the FilterTags dereference. The non-nil paths run through Compile,
  but a defensive nil-rule arrival isn't reachable that way.
- rule with no FilterTags / empty FilterTags map returns false (the
  check is len(FilterTags) > 0, so empty must classify as
  non-sensitive — pinning catches a flipped >= comparison).
- rule with a populated FilterTags returns true.

* fix(s3/lifecycle): Tick must requeue every drained Match on shutdown

Per codex review on #9413: Tick called Schedule.Drain to pop ALL due
matches at once, then iterated. If ctx canceled mid-loop, only the
current Match was re-added — everything past that index was silently
lost across the worker restart. With N due matches, up to N-1 were
dropped.

Fix: on cancellation, re-add due[i:] (current + remaining) before
returning. Matches already dispatched (due[:i]) stay processed; the
schedule is left exactly as it would be if Drain had returned only
the dispatched prefix.

Strengthen the existing test to enqueue three due matches and assert
sched.Len()==3 after a pre-canceled Tick. Pre-fix the test would have
seen Len()==1 because only the first popped Match was re-added.
2026-05-09 22:02:17 -07:00

46 lines
1.5 KiB
Go

package engine
import (
"testing"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle"
"github.com/stretchr/testify/assert"
)
// rulePredicateSensitive's non-nil branches are exercised by the
// existing Compile tests; this test pins the defensive nil-rule
// branch directly. Compile shouldn't ever pass a nil rule, but a
// future caller that does shouldn't panic.
func TestRulePredicateSensitive_NilRuleReturnsFalse(t *testing.T) {
assert.False(t, rulePredicateSensitive(nil))
}
func TestRulePredicateSensitive_NoFilterTagsReturnsFalse(t *testing.T) {
// A rule without FilterTags is not predicate-sensitive; the
// router's MatchPredicateChange path skips actions whose rule
// returns false here.
rule := &s3lifecycle.Rule{ID: "r", Status: s3lifecycle.StatusEnabled, ExpirationDays: 7}
assert.False(t, rulePredicateSensitive(rule))
}
func TestRulePredicateSensitive_EmptyFilterTagsReturnsFalse(t *testing.T) {
// An empty (non-nil) FilterTags map must classify as non-sensitive
// — the function checks len, so empty == 0 == false.
rule := &s3lifecycle.Rule{
ID: "r",
Status: s3lifecycle.StatusEnabled,
FilterTags: map[string]string{},
}
assert.False(t, rulePredicateSensitive(rule))
}
func TestRulePredicateSensitive_PopulatedFilterTagsReturnsTrue(t *testing.T) {
rule := &s3lifecycle.Rule{
ID: "r",
Status: s3lifecycle.StatusEnabled,
FilterTags: map[string]string{"env": "prod"},
}
assert.True(t, rulePredicateSensitive(rule))
}