| 1 | package extension |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "testing" |
| 8 | ) |
| 9 | |
| 10 | func TestRuntimeOwnerFallbackIsObservable(t *testing.T) { |
| 11 | before := RuntimeOwnerFallbackCount() |
| 12 | if got := RuntimeOwnerFromContext(context.Background()); got != DefaultRuntimeOwner { |
| 13 | t.Fatal("unbound context must use the compatibility owner") |
| 14 | } |
| 15 | if after := RuntimeOwnerFallbackCount(); after <= before { |
| 16 | t.Fatalf("fallback count = %d, want greater than %d", after, before) |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | func TestRuntimeOwnerRepeatedFileWritesKeepDistinctPriors(t *testing.T) { |
| 21 | owner := NewRuntimeOwner() |
| 22 | owner.Gate.Publish(7) |
| 23 | path := filepath.Join(t.TempDir(), "same.txt") |
| 24 | if err := os.WriteFile(path, []byte("old"), 0o644); err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | |
| 28 | first := owner.RecordFileWrite(path, true, []byte("old")) |
| 29 | if err := os.WriteFile(path, []byte("middle"), 0o644); err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | second := owner.RecordFileWrite(path, true, []byte("middle")) |
| 33 | if err := os.WriteFile(path, []byte("new"), 0o644); err != nil { |
| 34 | t.Fatal(err) |
| 35 | } |
| 36 | |
| 37 | if first == second { |
| 38 | t.Fatal("repeated writes must receive distinct receipt IDs") |
| 39 | } |
| 40 | if receipts := owner.Receipts.ForGeneration(7); len(receipts) != 2 { |
| 41 | t.Fatalf("receipts = %+v", receipts) |
| 42 | } |
| 43 | if err := owner.ApplyFileWriteCompensation(second); err != nil { |
| 44 | t.Fatal(err) |
| 45 | } |
| 46 | if got, _ := os.ReadFile(path); string(got) != "middle" { |
| 47 | t.Fatalf("second prior = %q", got) |
| 48 | } |
| 49 | if err := owner.ApplyFileWriteCompensation(first); err != nil { |
| 50 | t.Fatal(err) |
| 51 | } |
| 52 | if got, _ := os.ReadFile(path); string(got) != "old" { |
| 53 | t.Fatalf("first prior = %q", got) |
| 54 | } |
| 55 | for _, id := range []string{first, second} { |
| 56 | r, ok := owner.Receipts.Get(id) |
| 57 | if !ok || r.CompensationStatus != "applied" { |
| 58 | t.Fatalf("receipt %s = %+v, ok=%v", id, r, ok) |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | func TestRuntimeOwnerReceiptEvictionReleasesFilePrior(t *testing.T) { |
| 64 | owner := NewRuntimeOwner() |
| 65 | owner.Gate.Publish(11) |
| 66 | path := filepath.Join(t.TempDir(), "bounded.txt") |
| 67 | first := owner.RecordFileWrite(path, true, []byte("old")) |
| 68 | for i := 1; i <= defaultReceiptPerGenerationLimit; i++ { |
| 69 | owner.RecordFileWrite(path, true, []byte("old")) |
| 70 | } |
| 71 | |
| 72 | if _, ok := owner.Receipts.Get(first); ok { |
| 73 | t.Fatal("oldest receipt should be evicted at the per-generation limit") |
| 74 | } |
| 75 | if err := owner.FilePriors.Compensate(first); err == nil { |
| 76 | t.Fatal("evicted receipt must release its retained file prior") |
| 77 | } |
| 78 | if rec := owner.AssessRecoverability(11); rec.Clean { |
| 79 | t.Fatalf("truncated receipt history must not claim clean recovery: %+v", rec) |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | func TestRuntimeOwnerFilePriorBudgetMarksRecoveryTruncated(t *testing.T) { |
| 84 | owner := NewRuntimeOwner() |
| 85 | owner.FilePriors = newFilePriorStore(1, 1) |
| 86 | owner.Gate.Publish(12) |
| 87 | id := owner.RecordFileWrite(filepath.Join(t.TempDir(), "oversized"), true, []byte("12")) |
| 88 | receipt, ok := owner.Receipts.Get(id) |
| 89 | if !ok || receipt.CompensationStatus != "prior_truncated" { |
| 90 | t.Fatalf("receipt = %+v ok=%v, want prior_truncated", receipt, ok) |
| 91 | } |
| 92 | if rec := owner.AssessRecoverability(12); rec.Clean { |
| 93 | t.Fatalf("missing prior bytes must not claim clean recovery: %+v", rec) |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | func TestRuntimeOwnerReceiptEvictionReleasesMessageDedup(t *testing.T) { |
| 98 | owner := NewRuntimeOwner() |
| 99 | const gen = uint64(13) |
| 100 | if !owner.RecordMessageSentOnce(gen, "oldest", "test") { |
| 101 | t.Fatal("first message receipt was rejected") |
| 102 | } |
| 103 | for i := range defaultReceiptPerGenerationLimit { |
| 104 | owner.Receipts.Record(EffectReceipt{ |
| 105 | ID: "later-" + itoaU64(uint64(i)), |
| 106 | Generation: gen, |
| 107 | Class: Irreversible, |
| 108 | }) |
| 109 | } |
| 110 | if _, ok := owner.Receipts.Get("message-sent:oldest"); ok { |
| 111 | t.Fatal("oldest message receipt should have been evicted") |
| 112 | } |
| 113 | if !owner.RecordMessageSentOnce(gen, "oldest", "test") { |
| 114 | t.Fatal("evicted message receipt kept an unbounded dedup key") |
| 115 | } |
| 116 | } |
| 117 |